package vkmc;

import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.UnexpectedPage;
import com.gargoylesoftware.htmlunit.util.Cookie;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.EventObject;
import java.util.List;
import java.util.concurrent.*;

public class Crawler extends Thread
{

	private String Guid;
	private WebClient wb = new WebClient();

	private boolean LoggedIn;

	private LoggedIn le = new LoggedIn(this);

	private String user;
	private String pass;

	protected javax.swing.event.EventListenerList listenerList = new javax.swing.event.EventListenerList();

	public Crawler(String username, String password) throws IOException
	{
		user = username;
		pass = password;
		LoggedIn = false;
	}

	public boolean IsLoggedIn()
	{
		return LoggedIn;
	}

	public void Login()
	{
		start();
	}

	@SuppressWarnings({ "unchecked", "rawtypes", "unused" })
	public void run()
	{
        System.out.println("Logging in...");
		try
		{
			wb.setJavaScriptEnabled(false);
			wb.setCssEnabled(false);
			wb.setActiveXNative(false);
			wb.setAppletEnabled(false);
			wb.setThrowExceptionOnFailingStatusCode(false);

			WebRequest requestSettings = new WebRequest(new URL("http://login.vk.com/"), HttpMethod.POST);
			requestSettings.setRequestParameters(new ArrayList());
			requestSettings.getRequestParameters().add(new NameValuePair("act", "login"));
			requestSettings.getRequestParameters().add(new NameValuePair("success_url", ""));
			requestSettings.getRequestParameters().add(new NameValuePair("fail_url", ""));
			requestSettings.getRequestParameters().add(new NameValuePair("try_to_login", "1"));
			requestSettings.getRequestParameters().add(new NameValuePair("to", ""));
			requestSettings.getRequestParameters().add(new NameValuePair("vk", ""));
			requestSettings.getRequestParameters().add(new NameValuePair("al_test", "3"));
			requestSettings.getRequestParameters().add(new NameValuePair("email", user));
			requestSettings.getRequestParameters().add(new NameValuePair("pass", pass));
			requestSettings.getRequestParameters().add(new NameValuePair("expire", ""));

			HtmlPage page = wb.getPage(requestSettings);

			Guid = wb.getCookieManager().getCookie("remixsid").getValue();
            System.out.println("Got cookie: " + Guid);

			LoggedIn = true;
			raiseLoggedInChanged(le);
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}

	public Search Search(String keyword, int offset) throws MalformedURLException, IOException
	{
        System.out.println("Searching...");
		return new Search(keyword, offset, wb);
	}

	private List<Song> ParseSongs(HtmlPage page)
	{
        int numberOfCores = Runtime.getRuntime().availableProcessors();
        double blockingConstant = 0.9;
        int poolSize = (int)(numberOfCores / (1 - blockingConstant));
        final List<Callable<String>> tasks = new ArrayList<Callable<String>>();

		int i = 0;
		List<Song> result = new ArrayList<Song>();
        System.out.println("Parsing html");
		try
		{
			HtmlElement td = page.getElementById("results");
			for (HtmlElement el : td.getChildElements())
			{
				HtmlElement hUrl = (HtmlElement) el.getByXPath("//div[@class='area clear_fix']/table/tbody/tr/td/input").get(i);
				final String url = hUrl.getAttribute("value").split(",")[0];

				HtmlElement root = (HtmlElement) el.getByXPath("//td[@class='info']").get(i);
				HtmlElement artist = (HtmlElement) root.getByXPath("//div[@class='title_wrap fl_l']/b/a").get(i);
				HtmlElement title = (HtmlElement) root.getByXPath("//span[@class='title']/a").get(i);

				HtmlElement time = (HtmlElement) root.getByXPath("//div[@class='duration fl_r']").get(i);

                // Start new threading
                tasks.add(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        WebRequest requestSettings = new WebRequest(new URL(url), HttpMethod.HEAD);
                        UnexpectedPage p = wb.getPage(requestSettings);
                        return p.getWebResponse().getResponseHeaderValue("Content-Length");
                    }
                });
                // End new threading

                result.add(new Song(artist.asText() + " - " + title.asText(), 0, time.asText(), url, 0, hUrl.getAttribute("value").split(",")[1])); //Still need to load length and bitrate

				i++;

			}
		}
		catch (Exception ex)
		{
            ex.printStackTrace();
		}
        System.out.println("Calculating bitrate");

        final ExecutorService executorPool = Executors.newFixedThreadPool(poolSize);
        List<Future<String>> songLengths;
        songLengths = null;
        try {
            songLengths = executorPool.invokeAll(tasks, 10000, TimeUnit.SECONDS);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        i = 0;
        for(final Future<String> songLength : songLengths) {
            try {
                int length = Integer.parseInt(songLength.get());

                int bitrate = MapBitrate(length  * 8 / Integer.parseInt(result.get(i).Val) / 1000);
                result.get(i).Length = String.valueOf(length);
                result.get(i).BitRate = String.valueOf(bitrate);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            finally {
                i++;
            }
        }
        executorPool.shutdown();

		return result;
	}

	public int MapBitrate(int bitrate)
	{
		int[] mappings = { 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 192, 224, 256, 320 };
		for (int map : mappings)
		{
			if ((bitrate * 0.9) < map && (bitrate * 1.1) > map)
				return map;
		}
        if (bitrate >= 320)
            return 320;
		return bitrate;
	}

	public void addLoggedInListener(LoggedInListener listener)
	{
		listenerList.add(LoggedInListener.class, listener);
	}

	public void removeLoggedInListener(LoggedInListener listener)
	{
		listenerList.remove(LoggedInListener.class, listener);
	}

	void raiseLoggedInChanged(LoggedIn evt)
	{
		Object[] listeners = listenerList.getListenerList();
		for (int i = 0; i < listeners.length; i += 2)
		{
			if (listeners[i] == LoggedInListener.class)
			{
				((LoggedInListener) listeners[i + 1]).LoggedIn(evt);
			}
		}
	}

	@SuppressWarnings("serial")
	public class LoggedIn extends EventObject
	{
		public LoggedIn(Object src)
		{
			super(src);
		}
	}

	public interface LoggedInListener extends EventListener
	{
		public void LoggedIn(LoggedIn evt);

	}

	public class Search extends Thread
	{
		private String keyword;
		@SuppressWarnings("unused")
		private int offset;
		private WebClient wb;
		private List<Song> result = null;
		Found fe = new Found(this);

		protected javax.swing.event.EventListenerList listenerList = new javax.swing.event.EventListenerList();

		public Search(String keyword, int offset, WebClient wb)
		{
			wb.setJavaScriptEnabled(true);
			this.keyword = keyword;
			this.offset = offset;
			this.wb = wb;
			start();
		}

		public void addFoundListener(FoundListener listener)
		{
			listenerList.add(FoundListener.class, listener);
		}

		public void removeFounddListener(FoundListener listener)
		{
			listenerList.remove(FoundListener.class, listener);
		}

		void raiseFoundChanged(Found evt)
		{
			Object[] listeners = listenerList.getListenerList();
			for (int i = 0; i < listeners.length; i += 2)
			{
				if (listeners[i] == FoundListener.class)
				{
					((FoundListener) listeners[i + 1]).Found(evt);
				}
			}
		}

		@Override
		public void run()
		{
			try
			{
				find();
			}
			catch (Exception ex)
			{
                ex.printStackTrace();
            }
		}

		public void find() throws MalformedURLException, IOException
		{
			@SuppressWarnings("deprecation")
			WebRequest requestSettings = new WebRequest(new URL("http://vk.com/search?c[section]=audio&c[q]=" + URLEncoder.encode(keyword)), HttpMethod.POST);
			wb.getCookieManager().addCookie(new Cookie("remixsid", Guid));
			HtmlPage page = wb.getPage(requestSettings);

			result = ParseSongs(page);
			wb.closeAllWindows();
			raiseFoundChanged(fe);
		}

		public List<Song> getResult()
		{
			return result;
		}
	}

	@SuppressWarnings("serial")
	public class Found extends EventObject
	{
		public Found(Object src)
		{
			super(src);
		}
	}

	public interface FoundListener extends EventListener
	{
		public void Found(Found evt);
	}

	public class Song
	{
		public String Name = "-";
		public String Length = "0";
        public String Duration = "0";
		public String Url = "-";
		public String BitRate = "0";

        public String Val = "0";

		public Song(String name, int length, String duration, String url, int bitrate, String val)
		{
			this.Name = name;
			this.Length = String.valueOf(length);
            this.Duration = duration;
			this.Url = url;
			this.BitRate = String.valueOf(bitrate);
            this.Val = val;
		}

	}

}